Sync fork with upstream heygen-com/hyperframes (main → v0.7.109+) - #1
Open
TrevorLowing wants to merge 1847 commits into
Open
Sync fork with upstream heygen-com/hyperframes (main → v0.7.109+)#1TrevorLowing wants to merge 1847 commits into
TrevorLowing wants to merge 1847 commits into
Conversation
* fix(core): harden audio FX and group identity * fix(core): address audio group review feedback * fix(core): align preview transport with grouped audio * test(core): pin audio group gain ceiling * fix(core): preserve solo bridge through stack * fix(engine): harden grouped audio rendering * docs(engine): explain grouped mix fallback invariant * test(engine): allow grouped mixes to finish on Windows * feat(lint): validate audio group membership and timing * test(lint): pin audio group membership guards * fix(studio): unify audio IDs and group state * fix(studio): make audio-group edits transactional * fix(studio): keep preview state synchronized * fix(studio): align audio rows, automation lanes and headers * fix(studio): stabilize timeline audio derivations * refactor(studio): simplify group metadata memoization * style(studio): keep timeline layout within size gate * fix(studio): keep timeline preset apply off auditions
* fix(core): harden audio FX and group identity * fix(core): address audio group review feedback * fix(core): align preview transport with grouped audio * test(core): pin audio group gain ceiling * fix(core): preserve solo bridge through stack * fix(engine): harden grouped audio rendering * docs(engine): explain grouped mix fallback invariant * test(engine): allow grouped mixes to finish on Windows * feat(lint): validate audio group membership and timing * test(lint): pin audio group membership guards * fix(studio): unify audio IDs and group state * fix(studio): make audio-group edits transactional * fix(studio): keep preview state synchronized * fix(studio): align audio rows, automation lanes and headers * fix(studio): stabilize timeline audio derivations * refactor(studio): simplify group metadata memoization * style(studio): keep timeline layout within size gate * fix(studio): keep timeline preset apply off auditions * fix(studio): harden carve and FX rack behavior * fix(studio): repeat audio FX reveal requests
* fix(studio): reconnect property-panel audio controls * fix(studio): unify property panel audio detection * fix(studio): satisfy panel and deletion gates
* feat(studio,core)!: remove solo and the group meter * docs(audio): keep removal rationale current * refactor(core): retire studio solo bridge
* fix(core): harden audio FX and group identity * fix(core): address audio group review feedback * fix(core): align preview transport with grouped audio * test(core): pin audio group gain ceiling * fix(core): preserve solo bridge through stack * fix(engine): harden grouped audio rendering * docs(engine): explain grouped mix fallback invariant * test(engine): allow grouped mixes to finish on Windows * feat(lint): validate audio group membership and timing * test(lint): pin audio group membership guards * fix(studio): unify audio IDs and group state * fix(studio): make audio-group edits transactional * fix(studio): keep preview state synchronized * fix(studio): align audio rows, automation lanes and headers * fix(studio): stabilize timeline audio derivations * refactor(studio): simplify group metadata memoization * style(studio): keep timeline layout within size gate * fix(studio): keep timeline preset apply off auditions * fix(studio): harden carve and FX rack behavior * fix(studio): repeat audio FX reveal requests * fix(studio): reconnect property-panel audio controls * fix(studio): unify property panel audio detection * fix(studio): satisfy panel and deletion gates * feat(studio,core)!: remove solo and the group meter * docs(audio): keep removal rationale current * refactor(core): retire studio solo bridge * docs(audio): document grouped audio and its guardrails * docs(audio): point handoff at replacement stack
* fix(cli): scope overlap waiver to marked text * fix(skills): guard changelog caption rail * fix(skills): densify changelog caption checks * test(skills): satisfy strict seek typing
… again (#3463) seam-gate verify --project has failed with "preview server exited early" since #3310, which made `hyperframes preview` pick its launch mode from the TTY. The gate spawns with stdio [ignore,pipe,pipe], so interactive is false and it silently took the background path: the launcher exits 0 before the server is serving (read as a dead server), and the detached server escapes the gate's process-group cleanup (leaking a preview per run). Pass the CLI's own --foreground opt-out in the default --server-cmd. Both skill mirrors updated. — Rames
Grouped audio shipped and its three canaries were deleted rather than raised. Solo and the group meter shipped and were removed in the same week, so the digest says so. Notes that the v0.8.0 minor bump marks two catalog component removals rather than the week's headline. Generated with bun run changelog:weekly, then rewritten for publication. Every sha was machine-verified: 40 chars, prefix-matched, an ancestor of main, and inside the 2026-08-17..2026-08-25Z window. npx mint validate passes from docs/. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat(studio): drag automation segments * fix(studio): clear clip selection for group effects * fix(studio): replace clip selection with audio bus * fix(studio): make audio bus selection authoritative
chore: release v0.8.13
…dicts (#3468) The runtime absorbed a series of authoring mistakes over time and `runtime/init.ts` says so in its own comments, but the skills kept teaching the old rules. Four of them actively cost an agent a failing run: add `crossorigin` (lint rejects it unconditionally), never build a timeline inside `async` (lint calls that the documented contract), never `gsap.set` later-scene clips (two fixHints instruct exactly that), and 12 copyable media snippets with no `id`, which render silent. Corrected in every place each claim appeared, including `hyperframes-animation`, three workflow scripts, the scaffolded project instructions, the CLI `docs` command, and the public docs site: `data-track-index` is a Studio display lane the render never reads, `class="clip"` is a layout convention rather than a visibility requirement, timed elements may nest, the visibility window is half-open, sub-composition host dimensions are backfilled, and the root-fill rule applies only to the layered-composite path. Behaviour changes, each backed by a render rather than by reading code: - `timeline_registry_missing_init` deleted. The runtime creates the registry before any inline script; a composition without the guard line renders and animates correctly. - `video_nested_in_timed_element` kept, message corrected. A rendered repro shows the nested-with-local-start case really does break, so the rule guards a real defect, but nothing is "FROZEN": the extractor ignores the wrapper's offset while visibility uses it, so the clip shows wrong frames and then vanishes. - `mediaRenderIds` now stamps media whose source is a `<source>` child, closing a duplicate-id gap the old `[src]`-only selector left open. - Stale messages fixed on `subcomposition_root_styled_by_class` and `deprecated_data_layer`. `coreSkillContent.test.ts` pinned the literal sentence that made root `data-start` look required, so it is narrowed to structure plus the regression it genuinely catches. Not covered, and flagged in the PR: the media global-vs-local start heuristic in `runtime/init.ts` is the root cause behind the nested-video defect. Removing it changes the meaning of existing compositions and needs its own deprecation.
`registry_item_added` fires when a catalog block is installed and `render_complete` fires when a video is produced, but nothing joined them, so "did this video use the catalog?" had no answer. `hyperframes add` now records each installed item in `hyperframes.json` (installed files are plain composition HTML with no provenance marker, so this manifest is the only record that a file came from the registry), and `render_complete` reports both the items the project installed and the blocks the rendered composition actually reaches. An item installed and then never mounted was tried and dropped, which no add-time event can express. The scan answering "which sub-compositions does this file mount" now has one owner, `collectSubCompositionSrcs` in `@hyperframes/parsers`, shared with lint's `lintMissingOrEmptySubComposition`. It holds two invariants that were previously restated per call site and got re-derived wrongly: it is a text scan rather than a DOM query, because `<template>` content is inert and every sub-composition except the render entry is wrapped in one; and references resolve root-relative at every nesting level, matching `parseSubCompositions`. It walks tag by tag rather than running open-ended spans across the whole file, so a malformed composition cannot stall the render plan. Also: `registryItems` is declared in the config schema, which closes with `additionalProperties: false`, with an ajv-backed test pinning every key the CLI writes; counts are never truncated by the name cap, and the reported used blocks stay a subset of the reported installed ones, with `registry_items_truncated` marking a windowed list; and an unreadable manifest reports itself rather than posing as a project that never used the catalog.
`docs/changelog.mdx` reached 512 KB and started failing the 500 KB pre-commit check, so `chore: release v0.8.14` could only be committed by passing `HF_MAX_NONLFS_KB`. Every release from here would need the same override, and the file grows a few KB each time. The check is for binaries. Its own error message says "large binaries are being committed to git instead of LFS", and its header explains why: an ONNX model, HDR-regression MP4s, demo clips, each of which lives in history forever and is paid for by every clone. That cost is specific to binaries. Git delta-compresses text, so release notes that grow a few KB per commit add a few KB to the pack, while a binary of the same size re-enters the pack whole on every edit. Text is now exempt regardless of size, detected with `grep -I` (a file with NUL bytes is binary), the same heuristic git uses for "Binary files differ". The binary rule, the `registry/` exemption, the LFS check and the size threshold are all unchanged. The alternative was an allowlist entry for the one file, which would leave the next legitimately growing text file to hit the same wall and get the same one-off exemption. `scripts/check-large-files.sh` had no test. It has one now, wired into `test:scripts` so it actually runs: an over-limit binary fails, an over-limit text file passes, an under-limit binary passes, and multiple offenders are all named. Verified the text case fails with the exemption removed, so the test pins the behaviour rather than describing it.
Classify each <audio> element before Web Audio capture: same-origin, CORS-opted-in, or a non-http(s) scheme stays on the primary createMediaElementSource() path; cross-origin media without a crossorigin opt-in withholds that call (the Web Audio spec makes such a node output silence without throwing) and falls back to fetch + decodeAudioData, preserving the FX graph whenever the server allows CORS. Recheck the route at the transport's irreversible capture boundary, and account for currentSrc, src, and <source> candidates the same way the HTML resource-selection algorithm does. Emit a stable preview diagnostic (`runtime_web_audio_bypass`) at media discovery time, not only from playback scheduling, so `hyperframes check` — which seeks but never plays — can surface it as a `web_audio_bypass` finding. Diagnostics are suppressed during export rendering, where the producer mixes audio offline and already applies the FX chain. The existing non-unit-rate fail-closed rule stays scoped to fx-chain/automation so this fix does not newly mute grouped or above-unity tracks. Takes over #3459 with the data-native-audio escape hatch removed per review feedback: the automatic cross-origin detection already covers the cases that mattered, so the extra per-element opt-in attribute, its route-classifier branch, and its diagnostic path are dropped in favor of a single automatic behavior. Fixes #3458 Original-Author: desenmeng Co-Authored-By: desenmeng <desenmeng@users.noreply.github.com> Co-Authored-By: Miga <noreply@anthropic.com>
…gin, document gaps
fix: expose promoted template media slots
…aults fix: document safe template default editing
…hangs (#3479) Root cause: the per-worker capture calls in captureFrameRange (parallelCoordinator.ts) take no abort signal of their own, and only checked `signal.aborted` BEFORE starting each frame — a no-op once a worker is already awaiting an in-flight call. On WSL2, the native drawElement/BeginFrame capture call can hang indefinitely at frame 0 with no error. The DE parallel-router's existing stall watchdog (captureStreamingStage.ts) correctly fires `stallController.abort()` after HF_DE_STALL_MS, but that abort had no way to reach a worker already wedged inside a hung capture call — so executeParallelCapture's Promise.all waited forever, the render hung indefinitely, and the CLI's circuit breaker (which only runs after executeRenderJob settles) never got a chance to trip. Fix: race each per-frame capture call against the signal actually firing (raceAgainstAbort), the same "can't cancel, only race" pattern already used by the sequential capture path. Once the watchdog's abort is observed, the wedged worker rejects, executeParallelCapture settles, and the existing pinned-fallback retry / "reverted" outcome / circuit breaker machinery (already correct) runs end to end. Also widen the CLI breaker's trip condition from the literal string "reverted" to "not a clean routed success", so any future non-success outcome the observability layer records also latches the breaker instead of silently falling through. Closes #3441 Co-authored-by: Miga <noreply@anthropic.com>
…forcement-point claims
* fix(studio): claim upload filenames exclusively * fix(studio): write uploads through exclusive descriptors
Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
…#3794) The static-frame dedup predictor walked tween intervals from window.__timelines but was blind to onUpdate callbacks — motion driven from a timeline's onUpdate in a tween-free window was predicted static. The verifier compounded the gap by seeking with suppressEvents: true, so the onUpdate never fired and the frozen frame passed verification. Two changes: 1. Predictor: when a timeline carries vars.onUpdate, mark its full span as animated so those frames are never predicted static. 2. Verifier: seek with suppressEvents: false so the verification page behaves identically to the capture page. The verification page is already isolated (separate Page instance), so out-of-order event side effects cannot corrupt sequential capture. Fixes #3793
Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
* feat(registry): add 25 image carousel blocks (5 families × 5 variants) Five carousel families, each with 5 style variants: - Orbit (1–5): image cards on a spinning 3D Fibonacci sphere - Path (1–5): cards following animated CSS motion paths - Circle (1–5): circular carousel layouts - Vision (1–5): Apple Vision-style spatial presentations - Text Circle (1–5): circular carousels with text overlays All blocks are 1920×1080 at 6s, with 12–24 configurable image slots. Includes catalog preview thumbnails for each block. Co-Authored-By: Jake Moran <jake.moran@heygen.com> * refactor(registry): host carousel block images on the CDN The registry is served straight out of this repository (DEFAULT_REGISTRY_URL points at raw.githubusercontent.com), so every byte a block ships is permanent history. The 25 carousel blocks added 421 JPEGs, 27.7 MiB in a checkout, and made the diff 472 files. Only 23 of those images were distinct: the same 12-24 placeholders were copied into every block. files[] entries gain an optional `url`. When set, the installer fetches the bytes from there instead of joining the registry base. `path` does not change and still says where the file lands relative to the item, so composition HTML, target mirroring and `hyperframes add` behave exactly as before. Keys are content-addressed, so the 396 manifest entries resolve to 23 objects, and a changed image gets a new URL rather than a stale one cached behind `immutable, max-age=31536000`. The catalog preview renderer copies an item's directory and renders it, so it needs the same materialisation step. Without it the preview draws every card blank and reports success, which is worse than failing. Also drops registry/catalog/, 25 hand-made thumbnails referenced by nothing; catalog previews are rendered by CI and served from docs/images/catalog. Verified: all 23 objects return 200 from the CDN with hashes matching their keys; `hyperframes add carousel-orbit-1` against a local registry installs 24 real JPEGs; the preview render produces the album art, and produces blank cards when the fetch step is removed. * style(registry): format the carousel composition HTML `oxfmt --check .` covers the whole tree, and these 25 files were never run through it. The pre-commit hook only formats staged files, so nothing local caught it. * feat(catalog): publish the carousel blocks without republishing their images These 25 blocks had no Catalog page. Every other item in the registry has one, so they shipped invisible: installable by name, unfindable by browsing. Generating them naively undid the change they were added by. The Catalog payload copies an item's assets into docs/public/, which is tracked, so the 396 images this PR just removed came back as 43 MB one directory over — worse than the 3 MB they started as, because each block got its own copy. The copy exists because these compositions assemble `img.src` at run time out of a variable value, so there is no `src="..."` in the markup for the payload's asset scan to resolve. An unpredictable path can only be satisfied by serving every file beside it, which is what `needsOwnDirectory` asks for. An absolute URL needs no directory: the scan already skips any `https:` reference. So for the payload path only, hosted files are left undownloaded and the composition's variable defaults are rewritten to their URLs. The preview renderer still downloads them, because it paints real frames and a missing file is a blank card. The explorer posts every value to the preview frame on mount, including untouched ones, so the page's variable list carries the URLs too. Left as local paths they would have overridden the payload's own defaults and asked the frame for a file that was deliberately never published. Result: 25 pages, 25 payloads, zero bytes of image added. Verified: a spike item declaring no assets at all rendered its 24 covers from the CDN, proving the variable-default path; payload generation for a carousel block now writes no item directory and no shared asset; the preview render still produces the album art. mint validate and mint broken-links pass on the new pages. test:scripts is green. * refactor(catalog): split the hosted-asset step out of prepareProjectDir Two functions rather than one: finding the composition and rewriting its variable defaults are separate jobs, and inlining the mode branch pushed prepareProjectDir past the complexity gate it was already sitting on. Behaviour is unchanged. Re-verified both paths after the split: the payload for a carousel block still writes no item directory and no shared asset, and the preview render still produces the album art. * feat(catalog): give the carousels their own shelf 25 image carousels landed in Showcases and were 53% of it, so the scenes that shelf exists for disappeared underneath them. That is the same shape the 24 editor themes made, and it gets the same fix they got. Keyed on the first tag, which is this file's stated grouping rule, rather than on the name. `screen-flow-carousel` leads with `product-demo` and stays on the shelf that says what it is for; a future carousel that is not named `carousel-*` still lands here. Showcases 47 -> 22, Carousels 25, and no existing item changed shelf. * fix(registry): centre the circle-5 carousel path in its composition Its ring was centred at x=3832.6 in a 3840-wide composition, so it sat on the right edge and most of it fell outside the frame. Only a few cards were ever visible, cropped, with two thirds of the composition empty. The exported path carried absolute coordinates from a layout that was never recentred. Shifting the four vertices by (-1912.597, -4.340) puts the ring on the composition centre. Handles are relative, so only the anchor points move and the shape is unchanged. carousel-text-circle-5 shares the identical path and had the identical fault. The other three circle variants sit within 12% of centre, which reads as authored placement rather than the same bug, so they are left alone. * fix(catalog): rebuild the circle-5 payloads after recentring the path The Catalog preview plays the payload, not the composition on disk, so recentring the source changed nothing a reader sees. The payload still carried the old vertices and the ring still hung off the right edge of the frame. Verified the consumer this time, not just the producer: both payloads now resolve to a path centre of x=1920. The other 23 rebuild byte-identical, so the formatting pass did not reach them. * feat(catalog): promote Carousels to its own section It was a shelf inside Scenes & demos, which is where a scene type belongs by kind but not by weight. At 25 items it is larger than Data & charts (17) and Blocks (13), each of which is already a section holding a single shelf, so the catalog's own precedent puts it one level up. Pulling it out also takes the largest section in the catalog from 120 items to 95, which is the reason the shelf was added in the first place. The two circle-5 pages change because their embedded source block carries the recentred path; nothing else in them moved. --------- Co-authored-by: Jake Moran <jake.moran@heygen.com> Co-authored-by: Miguel Angel Simon Sierra <miguel.sierra@heygen.com>
…-retry fix(render): retry timed-out browser initialization once
…utability fix(cli): verify Chrome can execute during preflight
…alidation fix(engine): reject malformed JPEG input before encoding
…inspection docs(render): explain alpha-aware WebM inspection
* fix(core): escape generator metadata attributes * fix(parsers): retain decoded metadata while assigning ids * fix(parsers): preserve runtime html parser semantics * fix(parsers): canonicalize HTML attribute names for stable IDs * fix(parsers): normalize SVG attribute hashes across HTML parsers * fix(core): escape public resolution attribute values * fix(core): contain generated HTML CSS and script contexts * fix(core): preserve empty captions and document authored code trust
* fix(studio): contain project IDs across client and server routes * test(studio): use a portable project directory fixture * fix(studio): reject drive-relative IDs and filter project discovery
* fix(cli): validate captured image and font downloads * fix(cli): bound nested assets and preserve distinct fonts
* fix(cli): validate captured Lottie archives and previews * fix(cli): bound Lottie discovery before buffering * fix(cli): bound Lottie discovery lifecycle and archive paths * fix(cli): retain highest-priority Lottie candidates
* fix(cli): bound and validate registry installation * fix(cli): preserve registry install compatibility
* fix(player): authenticate slideshow navigation senders * test: isolate playground message boundary imports
* fix(security): verify CDN script integrity before inlining * fix(producer): normalize SRI algorithm case * fix(producer): abort compilation on integrity mismatch * fix(compiler): preserve nested script integrity requirements * fix(compiler): defer local inlining until integrity is known
… only when the composition changes (#3824) * perf(runtime): reuse one timing resolver per synchronous pass `createRuntimeStartTimeResolver` memoizes element start and duration lookups in WeakMaps, but `resolveStartForElement` and `resolveDurationForElement` each constructed a brand-new resolver per call and discarded it. The caches never served a second lookup, so a single pass re-walked the composition ancestry of every element, and per-seek work scaled with total timeline content rather than with what changed. `withTimingResolver(fn)` installs one resolver for the duration of a synchronous callback and restores the previous one in a `finally`, so a throw cannot leak the scope. Callers outside a scope keep today's construct-per-call behaviour unchanged. Three separate scopes, deliberately not one: - the media-cache build in `syncMediaForCurrentState` - the body of `syncTimedElementVisibility` - the media scan in `resolveMediaWindowDurationSeconds` They must stay separate. `syncRuntimeMedia` calls `el.load()` on the seek-past-buffered-range retry, which synchronously resets `el.duration` to NaN, and `resolveDurationForElement` reads `element.duration`. A cache spanning that write would serve the pre-`load()` duration to a post-`load()` read. Splitting at the write makes the staleness unrepresentable instead of merely handled. The third scope sits on `resolveMediaWindowDurationSeconds` rather than its caller `getSafeTimelineDurationSeconds`, which would look like the tidier boundary: that caller also invokes author-supplied `timeline.duration()` and third-party adapter `getInferredDurationSeconds()`, and foreign code inside a cache scope can mutate the DOM between two resolves. Resolver constructions per seek plus transport tick go from 15 per media element to a flat 4, and self time in `resolveStartForElementInternal` falls about 70% on a 79-video composition. * perf(runtime): derive composition duration only when the composition changes The runtime re-derived the composition's total duration on every animation frame. transportTick called getSafeTimelineDurationSeconds unconditionally, and deriving it scans every media element in the document and walks each one's composition ancestry to resolve an absolute start and duration. That value is a function of the composition, not of the playhead, so a paused editor with nothing happening recomputed the same answer ~60 times a second. On a 91-media-element composition the media scan ran 1.05 times per frame while the editor sat idle and untouched. Derive it once and reuse it until an input could have changed. The inputs, and the signal that catches each: - timing attributes edited (live editing, variables re-applied, the runtime's own autostamping) -> MutationObserver with an attribute filter - timed elements added or removed, including nested compositions that mount asynchronously after init -> the same observer, childList+subtree - media metadata arriving, or el.load() resetting duration to NaN, neither of which mutates the DOM -> capture-phase media event listeners - a timeline registered or lengthened in window.__timelines, a plain object nothing can observe -> a registry signature compared on read Observer records are delivered in a microtask, so the queue is drained on read as well; otherwise an edit read back in the same synchronous block, the ordinary live-editing case, would be served the pre-edit value. The render path does not read the cache at all. Capture depends on the exact duration and a frame rendered against a wrong one cannot be recovered, so it pays the full derivation on every frame exactly as before. Measured on the 91-media-element composition, idle and untouched: media scans per frame 1.05 -> 0.05. Across 100 single-frame seeks: 2.28 -> 0.11 per seek, so seeking does not invalidate the cache either. The residual is a separate per-20-tick caller in timeline.ts, untouched here.
…lk (#3831) The preview flattens several composition files into one DOM, so a layer's identity is its selector plus its occurrence index WITHIN its own source file. `getSourceScopedSelectorIndex` derived that per element: a whole -document `querySelectorAll(selector)`, `resolveSourceFile` on every match, then `indexOf`. Every element sharing a class paid for all of them, so a walk over n such elements did n whole-document queries and n^2 source-file resolutions — the shape a composition of repeated cards or tiles has by construction. Build the occurrence index ONCE per selector instead and share it across one walk. `withSelectorIndexPass(doc, run)` opens that scope; outside it the helper behaves exactly as before, per call. The pass lives in `collectDomEditLayerItems`, which owns the loop, rather than at a call site — its four callers (off-canvas indicators, the layers panel, the marquee hit-test and the agent look tool) all walked the same way and all paid the same cost. Behaviour is unchanged. The occurrence indices are identical, including the misses: an element outside the requested source file, or one not matching the selector, still yields undefined, as do `#`-prefixed and `[data-composition-id=` selectors and an invalid selector. Measured on a 1689-element preview over 80 single-frame seeks, per rebuild: class-selector document queries 171.6 -> 10.8, and the walk's self-timed cost 15.25ms -> 5.37ms. Elements walked per rebuild is unchanged at 973.7, so the two arms did the same work. Tests assert complexity invariance rather than a threshold: the query count must be IDENTICAL at n and 4n elements sharing a selector, which a fixture -sized threshold would not catch. Both fail on the previous algorithm.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Weekly fork sync
varry-llc/hyperframesis our maintained fallback for publicnpx hyperframes(used by the render action). This PR brings the fork'smainback in line with upstreamheygen-com/hyperframes.State detected by the weekly dep-radar task:
mainwas at upstream commit16eb1136(2026-07-09, PR feat(media-use): usage visibility — shared telemetry identity, miss log, resolve --stats heygen-com/hyperframes#2113 — a clean upstream commit, no fork-specific commits on top).mainis at67edb01(2026-08-16), which includes releases v0.7.106 → v0.7.109 and post-release commits.Interface check (INTEGRATIONS.md): none of v0.7.106–v0.7.109 removed or deprecated a flag the pipeline relies on (
browser ensure,lint,render --fps/--quality/--output,init --example,--version). So this sync is housekeeping — it does not trigger pinning the render action to the fork.Opened automatically by the weekly dependency & integration evaluator. Do not auto-merge — review and merge (or click "Sync fork") at your discretion. Ref: content-pipeline radar issue varry-llc/content-pipeline#15.